using System;
using csmic;
using PingPong.Generic;
using System.IO;
using System.Text;
using System.Linq;
using PingPong.GameEngine.Components;

namespace PingPong.GameEngine
{
    public class Engine
    {
		#region Members

		/// <summary>
		/// The input interpreter.
		/// </summary>
		private static Locked<Lazy<InputInterpreter>> inputInterpreter;/// 

		#endregion
		
		#region Properties
			
		/// <summary>
		/// Gets the expression parser.
		/// </summary>
		/// <value>
		/// The expression parser.
		/// </value>
        public static InputInterpreter ExpressionParser
        { 
            get
            {
                return inputInterpreter.Value.Value;
            }
        }
		
		#endregion
		
		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="PingPong.GameEngine.Engine"/> class.
		/// </summary>
		public Engine()
		{
			//
		}

		/// <summary>
		/// Initializes the <see cref="PingPong.GameEngine.Engine"/> class.
		/// </summary>
		static Engine()
		{
			inputInterpreter = new Locked<Lazy<InputInterpreter>>(new Lazy<InputInterpreter>(() => new InputInterpreter()));
		}

		#endregion

		#region Methods

		/// <summary>
		/// Parses the game definition file.
		/// </summary>
		/// <returns>The game definition file.</returns>
		/// <param name="path">Path.</param>
		public static GameDefinition ParseGameDefinitionFile(string path)
		{
			using (TextReader reader = new StreamReader(path))
			{
				return ParseGameDefinitionScript(reader.ToString());
			}
		}

		/// <summary>
		/// Parses the game definition script.
		/// </summary>
		/// <returns>The game definition script.</returns>
		/// <param name="script">Script.</param>
		public static GameDefinition ParseGameDefinitionScript(string script)
		{
			var encoder = new UTF8Encoding();
			var parser = new PingPong.GameEngine.Parser.Parser(new PingPong.GameEngine.Parser.Scanner(new MemoryStream(encoder.GetBytes(script))));
			parser.Parse();
			parser.GameDefinition.SetErrors(parser.errors.errorList);
			return parser.GameDefinition;
		}

		/// <summary>
		/// Creates the new state of the game.
		/// </summary>
		/// <returns>The new game state.</returns>
		/// <param name="gameDefinition">Game definition.</param>
		/// <param name="session">Session.</param>
		public static GameState CreateNewGameState(GameDefinition gameDefinition, Session session)
		{
			GameState gameState = new GameState();

			//Sanity checks.
			SanityCheck(gameDefinition, session);

			var teamGeneration = Enumerable.Range(1,gameDefinition.TeamCount)
				.Join(Enumerable.Range(1,gameDefinition.TeamSize),outer => true,inner => true,(outer, inner) => new { TeamNumber = outer, PlayerNumber = inner })
				.Zip(session.Players,(anonymous, player) => new {TeamNumber = anonymous.TeamNumber, PlayerNumber = anonymous.PlayerNumber, Player = player})
				.GroupBy(anonymous => anonymous.TeamNumber)
				.Select(orderedByTeam => new Team().Add(orderedByTeam.Select(s => s.Player)));

			gameState.AddTeamRange(teamGeneration)
				     .SetScoringType(gameDefinition.ScoringType)
				     .AddGameEventRange(gameDefinition.GameEvents);


			return gameState;
		}

		/// <summary>
		/// Performs a sanity check on the <see cref="GameDefinition"/> and <see cref="Session"/> are compatible.
		/// </summary>
		/// <param name="gameDefinition">Game definition.</param>
		/// <param name="session">Session.</param>
		public static void SanityCheck(GameDefinition gameDefinition, Session session)
		{
			if((gameDefinition.TeamCount * gameDefinition.TeamSize) != session.Players.Count())
			{
				throw new NotSupportedException("Cannot reconcile the team size and count to the number of players provided.");
			}
		}

		#endregion
    }
}

